//	Plazes-ASPI is an AS2.0 abstraction of the Plazes (http://www.plazes.com) XMLRPC-API
//		Copyright (C) 2006	Florian Mlodzik
//
//		This library is free software; you can redistribute it and/or
//		modify it under the terms of the GNU Lesser General Public
//		License as published by the Free Software Foundation; either
//		version 2.1 of the License, or (at your option) any later version.
//
//		This library is distributed in the hope that it will be useful,
//		but WITHOUT ANY WARRANTY; without even the implied warranty of
//		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the GNU
//		Lesser General Public License for more details.
//
//		You should have received a copy of the GNU Lesser General Public
//		License along with this library; if not, write to the Free Software
//		Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

// svn:keywords
// $URL: http://plazes-aspi.googlecode.com/svn/trunk/src/as2/com/orthogeneric/net/xmlrpc/XmlRpcMarshaller.as $
// $Id: XmlRpcMarshaller.as 4 2006-07-29 09:33:20Z florian.mlodzik $

/**
* The XmlRpcMarshaller-class is used for wrapping, serialization and deserialization of Xml remote procedure call and responses values.
* The implementation is static.
* The serialization/deserialization complies to the <a href="http://www.xmlrpc.com/spec">specification</a> with following type mappings:
* 
* AS <> XMLRPC
* 
* Boolean <> boolean
* String <> string
* Number <> int, i4, float
* Array <> array
* Object <> struct
* Date <> dateTime.iso8601
*
* Note: Base64 is not supported at all for marshalling and defaults to String for unmarshalling in this version!
*
* @author florian.mlodzik@gmail.com
* @version 0.9
* @see <a href="http://http://www.xmlrpc.com/">www.xmlrpc.com</a>
*/

// --> import
// <-- import

class com.orthogeneric.net.xmlrpc.XmlRpcMarshaller
{
	/**
	* The class-method to marshall the provided values to a XMLRPC-compliant Xml string.
	* 
	* @param rMethod The name of the remote method
	* @param args The values to be passed as parameters in the method call
	* @returns The XMLRPC compliant method call Xml string
	* 
	* 
	*/
	public static function marshallCall(rMethod:String, args:Array):String
	{
		var xmlStr:String = "<methodCall><methodName>" + rMethod + "</methodName><params>";
		var lgth = args.length;
		for (var i:Number = 0; i < lgth; i++)
		{
			xmlStr += "<param><value>" + _concatValue(args[i]) + "</value></param>";
		}
		xmlStr += "</params></methodCall>";
		
		return xmlStr;
	}
	
	/**
	* The class-method to unmarshall the received Xml method response to Actionscript types
	* 
	* @param doc The received Xml
	* @returns The serialized result
	* 
	* 
	*/
	public static function unmarshallResponse(doc:XML):Object
	{
		return _parseXml(doc.firstChild);
	}
	
	// --> marshalling class methods
	/**
	* The class-method to process the provided actionscript-type and value and return it as an XMLRPC compliant Xml string
	* 
	* @param val An Actionscript object containing value(s)
	* @returns A XMLRPC compliant Xml string to be used in a <value>
	* @see #_concatScalar()
	* @see #_concatArray()
	* @see #_concatStruct()
	* @see #_concatDate()
	* @exclude
	*/
	private static function _concatValue(val:Object):String
	{
		var concatStr:String;
		if (val instanceof Date) {
			concatStr = _concatDate(val);
		} else if (typeof(val) == "object") {
			concatStr = (val instanceof Array) ? _concatArray(val) : _concatStruct(val);
		} else {
			concatStr = _concatScalar(val);
		}
		
		return concatStr;
	}
	
	/**
	* The class-method to process the provided Actionscript scalar type and value and return it as an XMLRPC compliant Xml string
	* 
	* @param val An Actionscript object of type Boolean, String or Number
	* @returns A XMLRPC compliant Xml string to be used in a <value>
	* @see #_concatValue()
	* @exclude
	*/
	private static function _concatScalar(val:Object):String
	{
		var concatStr:String;
		switch (typeof(val)) {
			case "number" :	
				concatStr = ( val == Math.floor(Number(val)) ) ? "<int>" + val + "</int>" : "<double>" + val + "</double>";
				break;
			case "boolean" :
				concatStr = "<boolean>" + Number(val) + "</boolean>";
				break;
			case "string" :
				concatStr = "<string>" + val + "</string>";
				break;
			default :
				trace("NoValidType");
		}
		return concatStr;
	}
	
	/**
	* The class-method to process the provided Actionscript Date type and value and return it as an XMLRPC compliant Xml string
	* 
	* @param d An Actionscript object of type Date
	* @returns A XMLRPC compliant Xml string to be used in a <value>
	* @see #_concatValue()
	* @exclude
	*/
	private static function _concatDate(d:Object):String
	{
		var year:String = d.getFullYear().toString();
		var month:String = _addLeadingZero( (d.getMonth()+1).toString() );
		var day:String = _addLeadingZero( d.getDate().toString() );
		var hour:String = _addLeadingZero( d.getHours().toString() );
		var minute:String = _addLeadingZero( d.getMinutes().toString() );
		var second:String = _addLeadingZero( d.getSeconds().toString() );
		
		return "<dateTime.iso8601>" + year + month + day + "T" + hour + ":" + minute + ":" + second + "</dateTime.iso8601>";
	}
	
	/**
	* The class-helpermethod to add a leading zero to a number string when it is lower than 10
	* 
	* @param numStr The number string
	* @returns A number string with a leading zero if input lower than 10
	* @see #_concatDate()
	* @exclude
	*/
	private static function _addLeadingZero(numStr:String):String
	{
		return Number(numStr) < 10 ? "0" + numStr : numStr;
	}
	
	/**
	* The class-method to process the provided Actionscript Array type and its elements to return it as an XMLRPC compliant Xml string.
	* Every element in the array is evaluated using {@link XmlRpcMarshaller#_concatValue() XmlRpcMarshaller._concatValue()}.
	* 
	* @param arr An Actionscript object of type Array
	* @returns A XMLRPC compliant <array>...</array> Xml string to be used in a <value>
	* @see #_concatValue()
	* @exclude
	*/
	private static function _concatArray(arr:Object):String
	{
		var concatStr:String = "<array><data>";
		var lgth = arr.length
		for (var i:Number = 0; i < lgth; i++)
		{
			concatStr += "<value>" + _concatValue(arr[i]) + "</value>";
		}
		concatStr += "</data></array>";
		
		return concatStr;
	}
	
	/**
	* The class-method to process the provided Actionscript Object type and its properties to return it as an XMLRPC compliant Xml string.
	* Every property of the object is evaluated using {@link XmlRpcMarshaller#_concatValue() XmlRpcMarshaller._concatValue()}.
	* 
	* @param obj An Actionscript object of type Object
	* @returns A XMLRPC compliant <struct>...</struct> Xml string to be used in a <value>
	* @see #_concatValue()
	* @exclude
	*/
	private static function _concatStruct(obj:Object):String
	{
		var concatStr:String = "<struct>";
		for (var n:String in obj) 
		{
			concatStr += "<member><name>" + n + "</name><value>" + _concatValue(obj[n]) + "</value></member>";
		}
		concatStr += "</struct>";
		
		return concatStr;
	}
	// <-- marshalling class methods
	// --> unmarshalling class methods
	
	/**
	* The class-method to parse the provided XMLNode-instance to return it as an Actionscript object.
	* This method is called recursively to traverse the Xml node tree.
	* 
	* @param obj An Actionscript object of type Object
	* @param val The trailing object new values will be added to
	* @returns An object representing the values contained in the node tree
	* 
	* @see #unmarshallResponse()
	* @exclude
	*/
	private static function _parseXml(node:XMLNode, val:Object):Object
	{ 
			for (var sibNode = node; sibNode != null; sibNode = sibNode.nextSibling)
			{
					switch(sibNode.nodeName) {
						case "array" :
							return _parseXml(sibNode.firstChild, []);
						case "struct" :
							return _parseXml(sibNode.firstChild, {});
						case "member" :
							val[sibNode.firstChild.firstChild.nodeValue] = _parseXml(sibNode.firstChild.nextSibling);
							break;
						case "value" :
							if (sibNode.parentNode.nodeName == "data") {
								val.push( _parseXml(sibNode.firstChild) );
							} else {
								return _parseXml(sibNode.firstChild, val);
							}
							break;	 
						case null : 	
							return _serializeScalar(sibNode.nodeValue, sibNode.parentNode.nodeName);
			 			default :
			 				return _parseXml(sibNode.firstChild, val);
					}
			}
			return val;
	}
	
	/**
	* The class-method to serialize the provided XMLRPC scalar type and its value to Actionscript types.
	* Note that base64 type will be mapped to String.
	* 
	* @param val The value
	* @param type The XMLRPC scalar type string
	* @returns An Actionscript object with its type mapped to the XMLRPC scalar type
	* 
	* @see #_parseXml()
	* @exclude
	*/
	private static function _serializeScalar(val:String, type:String):Object
	{
		switch(type) 
		{
			case "boolean" :
				return Boolean(Number(val));
			case "int" :
			case "i4" :
			case "double" :
				return Number(val);
			case "dateTime.iso8601" :
				return _serializeDate(val);
			case "string" :
			case "base64" :
			case undefined:
			default:
				return val;
		}
	}
	
	/**
	* The class-method to serialize the provided XMLRPC scalar dateTime.iso8601 value to the Actionscript Date type.
	* 
	* @param val The dateTime.iso8601 string value
	* @returns An Actionscript Date instance holding the dateTime.iso8601 value
	*
	* @see #_serializeScalar()
	* @exclude
	*/
	private static function _serializeDate(val:String):Date
	{
			var val_arr:Array = val.split("T");
			var dateStr:String = val_arr[0];
			var time = val_arr[1] = val_arr[1].split(":");
			var year = Number( dateStr.slice(0,4) );
			var month = Number( dateStr.slice(4,6) )-1;
			var day = Number( dateStr.slice(6,8) );
			var hour = Number(time[0]);
			var minute = Number(time[1]);
			var second = Number( time[2].slice(0,2) );
			
			return new Date(year, month, day, hour, minute, second);
	}
}